項目22 型の絞り込みを理解する
絞り込みとは、TypeScriptが値の型を大まかなものからより具体的なものに移行するプロセスのこと
もっとも一般的な例がnullチェック
elemというシンボルがコードの場所によって異なる型を持つ。これはTypeScriptの特徴
code:ts
const elem = document.getElementById('what-time-is-it');// const elem: HTMLElement | null
if (elem) {
elem.innerHTML = 'Party Time'.blink();// const elem: HTMLElement
} else {
elem// const elem: null
alert('No element #what-time-is-it');
}
コンパイラーはコードの実行経路を追うため、制御フロー解析としても知られる
型チェッカーは、ロジックを追って条件式で型を絞り込むのが得意だが、エイリアスを作ることで妨害されることがある
詳しくは項目23 エイリアスを作成したら一貫してそれを使う
型を絞り込む方法
分岐の中でエラーを投げたり、returnしたりすると後続部分で変数の型が絞られる
code:ts
const elem = document.getElementById('what-time-is-it');// const elem: HTMLElement | null
if (!elem) throw new Error('Unable to find #what-time-is-it');
elem.innerHTML = 'Party Time'.blink();// const elem: HTMLElement
instanceofでの絞り込み
code:ts
function contains(text: string, search: string | RegExp) {
if (search instanceof RegExp) {
return !!search.exec(text);// (parameter) search: RegExp
}
return text.includes(search);// (parameter) search: string
}
プロパティのチェックによる絞り込み
code:ts
interface Apple { isGoodForBaking: boolean; }
interface Orange { numSlices: number; }
function pickFruit(fruit: Apple | Orange) {
if ('isGoodForBaking' in fruit) {
fruit// (parameter) fruit: Apple
} else {
fruit// (parameter) fruit: Orange
}
fruit// (parameter) fruit: Apple | Orange
}
型に明示的なタグをもたせる
タグ付きユニオンまたは、判別可能ユニオンを使う
code:ts
interface UploadEvent { type: 'upload'; filename: string; contents: string }
interface DownloadEvent { type: 'download'; filename: string; }
type AppEvent = UploadEvent | DownloadEvent;
function handleEvent(e: AppEvent) {
switch (e.type) {
case 'download':
console.log('Download', e.filename);// (parameter) e: DownloadEvent
break;
case 'upload':
console.log('Upload', e.filename, e.contents.length, 'bytes');// (parameter) e: UploadEvent
break;
}
}
ユーザー定義型ガードを使う
関数の戻り値に応じて型を絞り込むことができる
配列やオブジェクトの方を絞り込むことも可能
code:ts
function isInputElement(el: Element): el is HTMLInputElement {
return 'value' in el;
}
function getElementContent(el: HTMLElement) {
if (isInputElement(el)) {
return el.value;// (parameter) el: HTMLInputElement
}
return el.textContent;// (parameter) el: HTMLElement
}
⚠️型アサーションと同様に安全でないことに注意
型推論ではなく、実装者が型を制御していることを忘れない
TyepScirpt5.5から型述語の推論が導入された
明示的に変数 is 型を指定する必要がないケースがある
https://typescriptbook.jp/reference/functions/type-guard-functions#型述語
落とし穴
JavaScriptではtypeof nullは"object"を返すことを知らずに、意図しない絞り込みをしている可能性があること
偽値のプリミティブも同様の事のが起こりえる
code:ts
function maybeLogX(x?: number | string | null) {
if (!x) {
console.log(x);//(parameter) x: string | number | null | undefined
}
}
条件分岐で型が絞り込まれないケース
コールバック
コールバックが実行される頃にはobj.valueの値が変わる可能性があるため、絞り込みが効かない
code:ts
function logLaterIfNumber(obj: { value: string | number }) {
if (typeof obj.value === "number") {
setTimeout(() => console.log(obj.value.toFixed()));
// ~~~~~~~
// Property 'toFixed' does not exist on type 'string | number'.
// プロパティ 'toFixed' は型 'string | number' に存在しません。
}
}
const obj: { value: string | number } = { value: 123 };
logLaterIfNumber(obj);
obj.value = 'Cookie Monster';
#TypeScript